<!DOCTYPE html>

<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements.  See the NOTICE file
distributed with this work for additional information
regarding copyright ownership.  The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License.  You may obtain a copy of the License at

  http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied.  See the License for the
specific language governing permissions and limitations
under the License.
-->


<html>
	<head>
		<!-- <script src="sorttable.js"></script> -->
		<meta charset="UTF-8">
		<title>Traffic Monitor</title>
		<style>
		 body {
			 font-family: "Lato", sans-serif;
			 font-size: 14px;
		 }

		 ul.tab {
			 list-style-type: none;
			 margin: 0;
			 padding: 0;
			 overflow: hidden;
			 border: 1px solid #ccc;
			 background-color: #f1f1f1;
		 }

		 ul.tab li {float: left;}

		 ul.tab li a {
			 display: inline-block;
			 color: black;
			 text-align: center;
			 padding: 8px 8px;
			 text-decoration: none;
			 transition: 0.3s;
		 }

		 ul.tab li a:hover {
			 background-color: #cfd;
		 }

		 .tab-header-selected {
			 background-color: #adb;
		 }

		 .tabcontent {
			 display: none;
			 padding: 6px 6px;
			 border: 1px solid #ccc;
			 border-top: none;
		 }

		 table, th, td {
			 border: 0px solid black;
		 }

		 table {
			 border-collapse: separate;
			 border-spacing: 0px 0;
			 width: 100%;
		 }

		 th, td {
			 padding:5px 20px 5px 5px;
		 }

		 th {
			 white-space: nowrap;
		 }

		 tr.stripes:nth-child(even) {
			 background: #dfe
		 }
		 tr.stripes:nth-child(odd) {
			 background: #fff
		 }

		 li.endpoint {
			 margin: 4px 0;
		 }

		 ul {
			 list-style: none;
		 }

		 #top-bar {
			 border-collapse: collapse;
			 border-color: #adb;;
			 border-width: 0px 0px 1px 0px;
			 padding-bottom: 10px;
		 }

		 .links {
			 display: table;
		 }
		 .links ul {
			 display: table-cell;
			 vertical-align: top;
		 }

		 .error {
			 background-color: #f00;
		 }
		 .warning {
			 background-color: #f80;
		 }
		</style>
		<script>
		 function init() {
			 openTab('cache-states-content');
			 getTopBar();
			 setInterval(getCacheCount, 4755);
			 setInterval(getCacheAvailableCount, 4800);
			 setInterval(getBandwidth, 4621);
			 setInterval(getBandwidthCapacity, 4591);
			 setInterval(getCacheDownCount, 4832);
			 setInterval(getVersion, 10007); // change to retry on failure, and only do on startup
			 setInterval(getTrafficOpsUri, 10019); // change to retry on failure, and only do on startup
			 setInterval(getTrafficOpsCdn, 10500); // change to retry on failure, and only do on startup
			 setInterval(getEvents, 2004); // change to retry on failure, and only do on startup
			 setInterval(getCacheStatuses, 5009);
			 setInterval(getDsStats, 4003);
		 }

		 // source: http://stackoverflow.com/a/2901298/292623
		 function numberStrWithCommas(x) {
			 return x.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
		 }

		 function openTab(tabName) {
			 var i, x, tablinks;
			 x = document.getElementsByClassName("tabcontent");
			 for (i = 0; i < x.length; i++) {
				 x[i].style.display = "none";
			 }
			 tablinks = document.getElementsByClassName("tablinks");
			 for (i = 0; i < x.length; i++) {
				 tablinks[i].className = tablinks[i].className.replace(" tab-selected", "");
			 }

			 tabheaders = document.getElementsByClassName("tab-header");
			 for (i = 0; i < tabheaders.length; i++) {
				 tabheaders[i].className = tabheaders[i].className.replace(" tab-header-selected", "");
			 }

			 document.getElementById(tabName).style.display = "block";
			 document.getElementById(tabName).className += " tab-selected";
			 document.getElementById(tabName + "-tab").className += " tab-header-selected";
		 }

		 function getCacheCount() {
			 ajax("/api/cache-count", function(r) {
				 document.getElementById("cache-count").innerHTML = r;
			 });
		 }

		 function getCacheAvailableCount() {
			 ajax("/api/cache-available-count", function(r) {
				 document.getElementById("cache-available").innerHTML = r;
			 });
		 }

		 function getBandwidth() {
			 ajax("/api/bandwidth-kbps", function(r) {
				 document.getElementById("bandwidth").innerHTML = numberStrWithCommas((parseFloat(r) / kilobitsInGigabit).toFixed(2));
			 });
		 }

		 function getBandwidthCapacity() {
			 ajax("/api/bandwidth-capacity-kbps", function(r) {
				 document.getElementById("bandwidth-capacity").innerHTML = numberStrWithCommas((r / kilobitsInGigabit).toString());
			 });
		 }

		 function getCacheDownCount() {
			 ajax("/api/cache-down-count", function(r) {
				 document.getElementById("cache-down").innerHTML = r;
			 });
		 }

		 function getVersion() {
			 ajax("/api/version", function(r) {
				 document.getElementById("version").innerHTML = r;
			 });
		 }

		 function getTrafficOpsUri() {
			 ajax("/api/traffic-ops-uri", function(r) {
				 document.getElementById("source-uri").innerHTML = "<a href='" + r + "'>" + r + "</a>";
			 });
		 }


		 function getTrafficOpsCdn() {
			 ajax("/publish/ConfigDoc", function(r) {
				 var j = JSON.parse(r);
				 document.getElementById("cdn-name").innerHTML = j.cdnName;
			 });
		 }

		 var lastEvent = 0;
		 function getEvents() {
			 /// \todo add /api/events-since/{index} (and change Traffic Monitor to keep latest
			 ajax("/publish/EventLog", function(r) {
				 var jdata = JSON.parse(r);
				 for (i = jdata.events.length - 1; i >= 0; i--) {
					 var event = jdata.events[i];
					 if (event.index <= lastEvent) {
						 continue;
					 }
					 lastEvent = event.index
					 var row = document.getElementById("event-log").insertRow(1); //document.createElement("tr");
					 row.classList.add("stripes");
					 row.insertCell(0).id = row.id + "-name";
					 document.getElementById(row.id + "-name").textContent = event.name;
					 document.getElementById(row.id + "-name").style.whiteSpace = "nowrap";
					 row.insertCell(1).textContent = event.type;
					 row.insertCell(2).textContent = event.isAvailable ? "available" : "offline";
					 if(event.isAvailable) {
						 row.classList.add("stripes");
						 row.classList.remove("error");
					 } else {
						 row.classList.add("error");
						 row.classList.remove("stripes");
					 }
					 row.insertCell(3).textContent = event.description;
					 row.insertCell(4).id = row.id + "-last";
					 document.getElementById(row.id + "-last").textContent = new Date(event.time * 1000).toISOString();
					 document.getElementById(row.id + "-last").style.whiteSpace = "nowrap";
					 document.getElementById(row.id + "-last").style.textAlign = "right";
				 }
			 });
		 }

		 function getCacheStates() {
			 ajax("/api/cache-statuses", function(r) {
				 var jdata = JSON.parse(r);
				 var servers = Object.keys(jdata); //debug
				 var table = document.getElementById("cache-states");
				 for (i = 0; i < servers.length; i++) {
					 var server = servers[i];
					 if (!document.getElementById("cache-states-" + server)) {
						 var row = table.insertRow(1); //document.createElement("tr");
						 row.classList.add("stripes");
						 row.id = "cache-states-" + server
						 row.insertCell(0).id = row.id + "-server";
						 row.insertCell(1).id = row.id + "-type";
						 row.insertCell(2).id = row.id + "-status";
						 row.insertCell(3).id = row.id + "-load-average";
						 row.insertCell(4).id = row.id + "-query-time";
						 row.insertCell(5).id = row.id + "-health-time";
						 row.insertCell(6).id = row.id + "-stat-time";
						 row.insertCell(7).id = row.id + "-health-span";
						 row.insertCell(8).id = row.id + "-stat-span";
						 row.insertCell(9).id = row.id + "-bandwidth";
						 row.insertCell(10).id = row.id + "-connection-count";
						 document.getElementById(row.id + "-server").textContent = server;
						 document.getElementById(row.id + "-server").style.whiteSpace = "nowrap";
						 document.getElementById(row.id + "-load-average").style.textAlign = "right";
						 document.getElementById(row.id + "-query-time").style.textAlign = "right";
						 document.getElementById(row.id + "-health-time").style.textAlign = "right";
						 document.getElementById(row.id + "-stat-time").style.textAlign = "right";
						 document.getElementById(row.id + "-health-span").style.textAlign = "right";
						 document.getElementById(row.id + "-stat-span").style.textAlign = "right";
						 document.getElementById(row.id + "-bandwidth").style.textAlign = "right";
						 document.getElementById(row.id + "-connection-count").style.textAlign = "right";
					 }

					 /* \todo change to iterate over members, and make cells id constructed from these*/
					 if (jdata[server].hasOwnProperty("type")) {
						 document.getElementById("cache-states-" + server + "-type").textContent = jdata[server].type;
					 }
					 if (jdata[server].hasOwnProperty("status")) {
						 document.getElementById("cache-states-" + server + "-status").textContent = jdata[server].status;
						 var row = document.getElementById("cache-states-" + server);
						 if (jdata[server].status.indexOf("ADMIN_DOWN") != -1 || jdata[server].status.indexOf("OFFLINE") != -1) {
							 row.classList.add("warning");
							 row.classList.remove("error");
							 row.classList.remove("stripes");
						 } else if (jdata[server].status.indexOf(" available") == -1 && jdata[server].status.indexOf("ONLINE") != 0) {
							 row.classList.add("error");
							 row.classList.remove("warning");
							 row.classList.remove("stripes");
						 } else {
							 row.classList.add("stripe");
							 row.classList.remove("warning");
							 row.classList.remove("error");
						 }
					 }
					 if (jdata[server].hasOwnProperty("load_average")) {
						 document.getElementById("cache-states-" + server + "-load-average").textContent = jdata[server].load_average;
					 }
					 if (jdata[server].hasOwnProperty("query_time_ms")) {
						 document.getElementById("cache-states-" + server + "-query-time").textContent = jdata[server].query_time_ms;
					 }
					 if (jdata[server].hasOwnProperty("health_time_ms")) {
						 document.getElementById("cache-states-" + server + "-health-time").textContent = jdata[server].health_time_ms;
					 }
					 if (jdata[server].hasOwnProperty("stat_time_ms")) {
						 document.getElementById("cache-states-" + server + "-stat-time").textContent = jdata[server].stat_time_ms;
					 }
					 if (jdata[server].hasOwnProperty("health_span_ms")) {
						 document.getElementById("cache-states-" + server + "-health-span").textContent = jdata[server].health_span_ms;
					 }
					 if (jdata[server].hasOwnProperty("stat_span_ms")) {
						 document.getElementById("cache-states-" + server + "-stat-span").textContent = jdata[server].stat_span_ms;
					 }
					 if (jdata[server].hasOwnProperty("bandwidth_kbps")) {
						 var kbps = (jdata[server].bandwidth_kbps / kilobitsInMegabit).toFixed(2);
						 var max = numberStrWithCommas((jdata[server].bandwidth_capacity_kbps / kilobitsInMegabit).toFixed(0));
						 document.getElementById("cache-states-" + server + "-bandwidth").textContent = '' + kbps + ' / ' + max;
					 } else {
						 document.getElementById("cache-states-" + server + "-bandwidth").textContent = "N/A";
					 }
					 if (jdata[server].hasOwnProperty("connection_count")) {
						 document.getElementById("cache-states-" + server + "-connection-count").textContent = jdata[server].connection_count;
					 } else {
						 document.getElementById("cache-states-" + server + "-connection-count").textContent = "N/A";
					 }
				 }

				 for (var i = 1, row; row = table.rows[i]; i++) { // start at 1, because row[0] is the header
					 var server = row.id.replace(/^cache-states-/, '');
					 if(!(server in jdata)) {
						 table.deleteRow(i);
					 }
				 }

			 })
		 }

		 var millisecondsInSecond = 1000;
		 var kilobitsInGigabit = 1000000;
		 var kilobitsInMegabit = 1000;

		 // dsDisplayFloat takes a float, and returns the string to display. For nonzero values, it returns two decimal places. For zero values, it returns an empty string, to make nonzero values more visible.
		 function dsDisplayFloat(f) {
			 var s = f
			 if (f != 0.0) {
				 s = f.toFixed(2);
			 }
			 return s
		 }

		 function getDsStats() {
			 var now = Date.now();

			 /// \todo add /api/delivery-service-stats which only returns the data needed by the UI, for efficiency
			 ajax("/publish/DsStats", function(r) {
				 var j = JSON.parse(r);
				 var jds = j.deliveryService
				 var deliveryServiceNames = Object.keys(jds); //debug
				 //decrementing for loop so DsNames are alphabetical A-Z
				 //TODO allow for filtering of columns so this isn't necessary
					for (var i = deliveryServiceNames.length - 1; i >= 0; i--) {
					 var deliveryService = deliveryServiceNames[i];

					 if (!document.getElementById("deliveryservice-stats-" + deliveryService)) {
						 var row = document.getElementById("deliveryservice-stats").insertRow(1); //document.createElement("tr");
						 row.id = "deliveryservice-stats-" + deliveryService
						 row.insertCell(0).id = row.id + "-delivery-service";
						 row.insertCell(1).id = row.id + "-status";
						 row.insertCell(2).id = row.id + "-caches-reporting";
						 row.insertCell(3).id = row.id + "-bandwidth";
						 row.insertCell(4).id = row.id + "-tps";
						 row.insertCell(5).id = row.id + "-2xx";
						 row.insertCell(6).id = row.id + "-3xx";
						 row.insertCell(7).id = row.id + "-4xx";
						 row.insertCell(8).id = row.id + "-5xx";
						 row.insertCell(9).id = row.id + "-disabled-locations";
						 document.getElementById(row.id + "-delivery-service").textContent = deliveryService;
						 document.getElementById(row.id + "-delivery-service").style.whiteSpace = "nowrap";
						 document.getElementById(row.id + "-caches-reporting").style.textAlign = "right";
						 document.getElementById(row.id + "-bandwidth").style.textAlign = "right";
						 document.getElementById(row.id + "-tps").style.textAlign = "right";
						 document.getElementById(row.id + "-2xx").style.textAlign = "right";
						 document.getElementById(row.id + "-3xx").style.textAlign = "right";
						 document.getElementById(row.id + "-4xx").style.textAlign = "right";
						 document.getElementById(row.id + "-5xx").style.textAlign = "right";
					 }

					 // \todo check that array has a member before dereferencing [0]
					 if (jds[deliveryService].hasOwnProperty("isAvailable")) {
						 document.getElementById("deliveryservice-stats-" + deliveryService + "-status").textContent = jds[deliveryService]["isAvailable"][0].value == "true" ? "available" : "unavailable - " + jds[deliveryService]["error-string"][0].value;
					 }
					 if (jds[deliveryService].hasOwnProperty("caches-reporting") && jds[deliveryService].hasOwnProperty("caches-available") && jds[deliveryService].hasOwnProperty("caches-configured")) {
						 document.getElementById("deliveryservice-stats-" + deliveryService + "-caches-reporting").textContent = jds[deliveryService]['caches-reporting'][0].value + " / " + jds[deliveryService]['caches-available'][0].value + " / " + jds[deliveryService]['caches-configured'][0].value;
					 }
					 if (jds[deliveryService].hasOwnProperty("total.kbps")) {
						 document.getElementById("deliveryservice-stats-" + deliveryService + "-bandwidth").textContent = (jds[deliveryService]['total.kbps'][0].value / kilobitsInMegabit).toFixed(2);
					 } else {
						 document.getElementById("deliveryservice-stats-" + deliveryService + "-bandwidth").textContent = "N/A";
					 }
					 if (jds[deliveryService].hasOwnProperty("total.tps_total")) {
						 document.getElementById("deliveryservice-stats-" + deliveryService + "-tps").textContent = dsDisplayFloat(parseFloat(jds[deliveryService]['total.tps_total'][0].value));
					 } else {
						 document.getElementById("deliveryservice-stats-" + deliveryService + "-tps").textContent = "N/A";
					 }
					 if (jds[deliveryService].hasOwnProperty("total.tps_2xx")) {
						 document.getElementById("deliveryservice-stats-" + deliveryService + "-2xx").textContent = dsDisplayFloat(parseFloat(jds[deliveryService]['total.tps_2xx'][0].value));
					 } else {
						 document.getElementById("deliveryservice-stats-" + deliveryService + "-2xx").textContent = "N/A";
					 }
					 if (jds[deliveryService].hasOwnProperty("total.tps_3xx")) {
						 document.getElementById("deliveryservice-stats-" + deliveryService + "-3xx").textContent = dsDisplayFloat(parseFloat(jds[deliveryService]['total.tps_3xx'][0].value));
					 } else {
						 document.getElementById("deliveryservice-stats-" + deliveryService + "-3xx").textContent = "N/A";
					 }
					 if (jds[deliveryService].hasOwnProperty("total.tps_4xx")) {
						 document.getElementById("deliveryservice-stats-" + deliveryService + "-4xx").textContent = dsDisplayFloat(parseFloat(jds[deliveryService]['total.tps_4xx'][0].value));
					 } else {
						 document.getElementById("deliveryservice-stats-" + deliveryService + "-4xx").textContent = "N/A";
					 }
					 if (jds[deliveryService].hasOwnProperty("total.tps_5xx")) {
						 document.getElementById("deliveryservice-stats-" + deliveryService + "-5xx").textContent = dsDisplayFloat(parseFloat(jds[deliveryService]['total.tps_5xx'][0].value));
					 } else {
						 document.getElementById("deliveryservice-stats-" + deliveryService + "-5xx").textContent = "N/A";
					 }

					 // \todo implement disabled locations

					 var row = document.getElementById("deliveryservice-stats-" + deliveryService);
					 if (jds[deliveryService]["isAvailable"][0].value == "true") {
						 row.classList.add("stripes");
						 row.classList.remove("error");
					 } else {
						 row.classList.add("error");
						 row.classList.remove("stripes");
					 }
				 }
			 })
		 }

		 function getCacheStatuses() {
			 getCacheCount();
			 getCacheAvailableCount();
			 getCacheDownCount();
			 getCacheStates();
		 }

		 function getTopBar() {
			 getVersion();
			 getTrafficOpsUri();
			 getTrafficOpsCdn();
			 getCacheStatuses();
		 }

		 function ajax(endpoint, f) {
			 var xhttp = new XMLHttpRequest();
			 xhttp.onreadystatechange = function() {
				 if (xhttp.readyState == 4 && xhttp.status == 200) {
					 f(xhttp.responseText);
				 }
			 };
			 xhttp.open("GET", endpoint, true);
			 xhttp.send();
		 }
		</script>
	</head>
	<body onload="init()">

		<table id="top-bar">
			<tr>
				<td>Caches: count=<span id="cache-count">0</span> available=<span id="cache-available">0</span> down=<span id="cache-down">0</span> </td>
				<td>Bandwidth: <span id="bandwidth">0</span> / <span id="bandwidth-capacity">∞</span> gbps</td>
				<td>Traffic Ops: <span id="source-uri">unknown</span></td>
				<td>CDN: <span id="cdn-name">unknown</span></td>
				<td>Version: <span id="version">unknown</span></td>
			</tr>
		</table>

		<div class="links">
			<ul>
				<li class="endpoint"><a href="/publish/EventLog">EventLog</a></li>
				<li class="endpoint"><a href="/publish/CacheStats">CacheStats</a></li>
				<li class="endpoint"><a href="/publish/DsStats">DsStats</a></li>
				<li class="endpoint"><a href="/publish/CrStates">CrStates (as published to Traffic Routers)</a></li>
				<li class="endpoint"><a href="/publish/CrConfig">CrConfig (json)</a></li>
				<li class="endpoint"><a href="/publish/PeerStates">PeerStates</a></li>
				<li class="endpoint"><a href="/publish/Stats">Stats</a></li>
				<li class="endpoint"><a href="/publish/StatSummary">StatSummary</a></li>
				<li class="endpoint"><a href="/publish/ConfigDoc">ConfigDoc</a></li>
			</ul>

			<ul>
				<li class="endpoint"><a href="/api/cache-count">/api/cache-count</a></li>
				<li class="endpoint"><a href="/api/cache-available-count">/api/cache-available-count</a></li>
				<li class="endpoint"><a href="/api/cache-down-count">/api/cache-down-count</a></li>
				<li class="endpoint"><a href="/api/version">/api/version</a></li>
				<li class="endpoint"><a href="/api/traffic-ops-uri">/api/traffic-ops-uri</a></li>
				<li class="endpoint"><a href="/api/cache-statuses">/api/cache-statuses</a></li>
				<li class="endpoint"><a href="/api/bandwidth-kbps">/api/bandwidth-kbps</a></li>
				<li class="endpoint"><a href="/api/bandwidth-capacity-kbps">/api/bandwidth-capacity-kbps</a></li>
				<li class="endpoint"><a href="/api/monitor-config">/api/monitor-config</a></li>
				<li class="endpoint"><a href="/api/crconfig-history">/api/crconfig-history</a></li>
			</ul>
		</div>

		<ul class="tab">
			<li id="cache-states-content-tab" class="tab-header"><a href="#" onclick="openTab('cache-states-content')" class="tablinks">Cache States</a></li>
			<li id="deliveryservice-stats-content-tab" class="tab-header"><a href="#" onclick="openTab('deliveryservice-stats-content')" class="tablinks">Delivery Service States</a></li>
			<li id="event-log-content-tab" class="tab-header"><a href="#" onclick="openTab('event-log-content')" class="tablinks">Event Log</a></li>
		</ul>

		<div id="cache-states-content" class="tabcontent">
			<table id="cache-states" class="tab-grid sortable">
				<tr>
					<th>Server</th>
					<th>Type</th>
					<th>Status</th>
					<th align="right">Load Average</th>
					<th align="right">Query Time (ms)</th>
					<th align="right">Health Time (ms)</th>
					<th align="right">Stat Time (ms)</th>
					<th align="right">Health Span (ms)</th>
					<th align="right">Stat Span (ms)</th>
					<th align="right">Bandwidth (mbps)</th>
					<th align="right">Connection Count</th>
				</tr>
			</table>
		</div>
		<div id="deliveryservice-stats-content" class="tabcontent">
			<table id="deliveryservice-stats" class="tab-grid sortable">
				<tr>
					<th>Delivery Service</th>
					<th>Status</th>
					<th align="right">Caches Reporting/Available/Configured</th>
					<th align="right">Bandwidth (mbps)</th>
					<th align="right">t/sec</th>
					<th align="right">2xx/sec</th>
					<th align="right">3xx/sec</th>
					<th align="right">4xx/sec</th>
					<th align="right">5xx/sec</th>
					<th>Disabled Locations</th>
				</tr>
			</table>
		</div>

		<div id="event-log-content" class="tabcontent">
			<table id="event-log" class="tab-grid sortable">
				<tr>
					<th>Name</th>
					<th>Type</th>
					<th>Status</th>
					<th>Description</th>
					<th align="center" id="event-log-last-header">Event Time</th>
				</tr>
			</table>
		</div>

		<div id="update-num-text">Number of updates: <span id="update-num">0</span></div>
		<div id="last-val-text">Last Val: <span id="last-val">0</span></div>
		<a href="/">Refresh Server List</a>
	</body>
</html>
